package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/proxy/grpcproxy"
	assetfs "github.com/elazarl/go-bindata-assetfs"
	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"go-programming-tour-book/tag-service/pkg/swagger"
	pb "go-programming-tour-book/tag-service/proto"
	"go-programming-tour-book/tag-service/server"
	"golang.org/x/net/http2"
	"golang.org/x/net/http2/h2c"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/reflection"
	"google.golang.org/grpc/status"
	"log"
	"net/http"
	"path"
	"strings"
	"time"
)

//var grpcPort string
//var httpPort string
//
//func init() {
//	flag.StringVar(&grpcPort, "grpc_port", "8001", "gRPC 启动端口号") //gRPC 协议本质是 HTTP/2
//	flag.StringVar(&httpPort, "http_port", "8002", "HTTP 启动端口号")
//	flag.Parse()
//}
//
//func main() {
//	//声明一个 chan 用于接受 goroutine 的 err 信息
//	errs := make(chan error)
//	go func() {
//		err := RunHttpServer(httpPort)
//		if err != nil {
//			errs <- err
//		}
//	}()
//	/*
//		测试：
//		 	$ curl http://127.0.0.1:8002/ping
//			pong
//	*/
//	go func() {
//		err := RunGrpcServer(grpcPort)
//		if err != nil {
//			errs <- err
//		}
//	}()
//	/*
//		测试：
//		$ grpcurl -plaintext localhost:8001 proto.TagService.GetTagList
//		{
//		  "list": [
//			{
//			  "id": "1",
//			  "name": "Golang"
//			}
//		  ],
//		  "pager": {
//			"page": "1",
//			"pageSize": "10",
//			"totalRows": "1"
//		  }
//		}
//	*/
//
//	select {
//	case err := <-errs:
//		log.Fatalf("Run Server err: %v", err)
//	}
//}
//
//func RunHttpServer(port string) error {
//	//初始化一个新的 HTTP 多路复用器
//	serveMux := http.NewServeMux()
//	//新增一个 /ping 路由及其 Handler，可用于做基本的心跳检测
//	serveMux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
//		_, _ = w.Write([]byte(`pong`))
//	})
//	return http.ListenAndServe(":"+port, serveMux)
//}
//
//func RunGrpcServer(port string) error {
//	s := grpc.NewServer()
//	pb.RegisterTagServiceServer(s, server.NewTagServer())
//	reflection.Register(s)
//	lis, err := net.Listen("tcp", ":"+port)
//	if err != nil {
//		return err
//	}
//	return s.Serve(lis)
//}

//var port string
//
//func init() {
//	flag.StringVar(&port, "port", "8003", "启动端口号")
//	flag.Parse()
//}
//
//func main() {
//	l, err := RunTCPServer(port)
//	if err != nil {
//		log.Fatalf("Run TCP Sercer err: %v", err)
//	}
//
//	//创建一个新的 cmux 对象
//	m := cmux.New(l)
//	grpcL := m.MatchWithWriters(
//		cmux.HTTP2MatchHeaderFieldPrefixSendSettings( //该条件指定了要匹配的头字段名称为 "content-type"，并且它的值必须以 "application/grpc" 作为前缀。
//			"content-type",
//			"application/grpc",
//		),
//	)
//	httpL := m.Match(cmux.HTTP1Fast())
//
//	grpcS := RunGrpcServer()
//	httpS := RunHttpServer(port)
//	go grpcS.Serve(grpcL)
//	go httpS.Serve(httpL)
//
//	err = m.Serve()
//	if err != nil {
//		log.Fatalf("Run Serve err: %v", err)
//	}
//}
//
//func RunTCPServer(port string) (net.Listener, error) {
//	return net.Listen("tcp", ":"+port)
//}
//
//func RunGrpcServer() *grpc.Server {
//	s := grpc.NewServer()
//	pb.RegisterTagServiceServer(s, server.NewTagServer())
//	//注册服务
//	reflection.Register(s)
//	return s
//}
//
//func RunHttpServer(port string) *http.Server {
//	//初始化一个新的 HTTP 多路复用器
//	serveMux := http.NewServeMux()
//	serveMux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
//		_, _ = w.Write([]byte(`pong`))
//	})
//
//	return &http.Server{
//		Addr:    ":" + port,
//		Handler: serveMux,
//	}
//}

var port string

type httpError struct {
	Code    int32  `json:"code,omitempty"`
	Message string `json:"message,omitempty"`
}

func init() {
	flag.StringVar(&port, "port", "8004", "启动端口号")
	flag.Parse()
}

// SERVICE_NAME 当前服务的服务名
const SERVICE_NAME = "tag-service"

func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
	/*
		在整体的方法逻辑上来讲，关键之处在于调用了 h2c.NewHandler 方法进行了特殊处理，
		h2c.NewHandler 会返回一个 http.handler，其主要是在内部逻辑是拦截了所有 h2c 流量，
		然后根据不同的请求流量类型将其劫持并重定向到相应的 Hander 中去处理，最终以此达到同个端口上既提供 HTTP/1.1 又提供 HTTP/2 的功能了。
	*/
	//对 gRPC 服务的非加密模式的设置。 h2c 标识允许通过明文 TCP 运行 HTTP/2 的协议
	return h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		//ProtoMajor 代表客户端请求的版本号，客户端始终使用 HTTP/1.1 或 HTTP/2 协议
		//对 Content-Type 进行判断，通过 grpc 的标志位 application/grpc 来确定流量的类型
		if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
			grpcServer.ServeHTTP(w, r)
		} else {
			otherHandler.ServeHTTP(w, r)
		}
	}), &http2.Server{})
}

func RunServer(port string) error {
	httpMux := runHttpServer()
	grpcS := runGrpcServer()
	gatewayMux := runGrpcGatewayServer()

	httpMux.Handle("/", gatewayMux)

	etcdClient, err := clientv3.New(clientv3.Config{
		Endpoints:   []string{"http://192.168.239.128:2379"},
		DialTimeout: time.Second * 60,
	})
	if err != nil {
		return err
	}
	defer etcdClient.Close()

	target := fmt.Sprintf("/etcdv3://go-programming-tour/grpc/%s", SERVICE_NAME)
	grpcproxy.Register(etcdClient, target, ":"+port, 60)

	return http.ListenAndServe(":"+port, grpcHandlerFunc(grpcS, httpMux))
}

func runHttpServer() *http.ServeMux {
	serveMux := http.NewServeMux()
	serveMux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
		_, _ = w.Write([]byte(`/pong`))
	})

	prefix := "/swagger-ui/"
	fileServer := http.FileServer(&assetfs.AssetFS{
		Asset:    swagger.Asset,
		AssetDir: swagger.AssetDir,
		Prefix:   "third_party/swagger-ui",
	})
	serveMux.Handle(prefix, http.StripPrefix(prefix, fileServer))
	serveMux.HandleFunc("/swagger/", func(w http.ResponseWriter, r *http.Request) {
		if !strings.HasSuffix(r.URL.Path, "swagger.json") {
			http.NotFound(w, r)
			return
		}

		p := strings.TrimPrefix(r.URL.Path, "/swagger/")
		p = path.Join("proto", p)

		http.ServeFile(w, r, p)
	})

	return serveMux
}

func runGrpcServer() *grpc.Server {
	//grpc.ServerOption 设置 gRPC Server 的相关属性，如 credentials、keepalive 等参数
	//服务端拦截器也在此注册，但是需要以指定的类型进行封装，例如一元拦截器是使用 grpc.UnaryInterceptor
	opts := []grpc.ServerOption{
		grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
			HelloInterceptor,
			WorldInterceptor,
		)),
	}

	s := grpc.NewServer(opts...)
	pb.RegisterTagServiceServer(s, server.NewTagServer())
	reflection.Register(s)

	return s
}

func HelloInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
	log.Println("你好，喜羊羊")
	resp, err := handler(ctx, req)
	log.Println("再见，喜羊羊")
	return resp, err
}

func WorldInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
	log.Println("你好，青青草原")
	resp, err := handler(ctx, req)
	log.Println("再见，青青草原")
	return resp, err
}

func runGrpcGatewayServer() *runtime.ServeMux {
	endpoint := "0.0.0.0:" + port
	runtime.HTTPError = grpcGatewayError
	gwmux := runtime.NewServeMux()
	//grpc.DialOption 中通过设置 grpc.WithInsecure() 指定 Server 为非加密模式，否则程序在运行时会出现问题，因为 gRPC Server/Client 在启动和调用时，必须明确其是否加密
	dopts := []grpc.DialOption{grpc.WithInsecure()}
	//RegisterTagServiceHandlerFromEndpoint 注册 TagServiceHandler 事件，其内部会自动转换并拨号到 gRPC Endpoint，并在上下文结束后关闭连接
	_ = pb.RegisterTagServiceHandlerFromEndpoint(context.Background(), gwmux, endpoint, dopts)

	return gwmux
}

func main() {
	err := RunServer(port)
	if err != nil {
		log.Fatalf("Run Serve err: %v", err)
	}
	/*
		//心跳检测
		$ curl http://127.0.0.1:8004/ping
		/pong

		//RPC 方法的 HTTP/1.1 响应
		$ curl http://127.0.0.1:8004/api/v1/tags
		{"list":[{"id":"1","name":"Golang"}],"pager":{"page":"1","page_size":"10","total_rows":"1"}}

		//RPC 方法的 gRPC（HTTP/2） 响应
		$ grpcurl -plaintext localhost:8004 proto.TagService.GetTagList
		{
		  "list": [
		    {
		      "id": "1",
		      "name": "Golang"
		    }
		  ],
		  "pager": {
		    "page": "1",
		    "pageSize": "10",
		    "totalRows": "1"
		  }
		}


	*/
}

func grpcGatewayError(ctx context.Context, _ *runtime.ServeMux, marshaler runtime.Marshaler, w http.ResponseWriter, _ *http.Request, err error) {
	s, ok := status.FromError(err)
	if !ok {
		s = status.New(codes.Unknown, err.Error())
	}

	httpError := httpError{Code: int32(s.Code()), Message: s.Message()}
	details := s.Details()
	for _, detail := range details {
		if v, ok := detail.(*pb.Error); ok {
			httpError.Code = v.Code
			httpError.Message = v.Message
		}
	}

	resp, _ := json.Marshal(httpError)
	w.Header().Set("Content-Type", marshaler.ContentType())
	w.WriteHeader(runtime.HTTPStatusFromCode(s.Code()))
	_, _ = w.Write(resp)
}
